package net.kuruvila.php.tool;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.kuruvila.php.parser.IndentationParser;
import net.kuruvila.php.parser.Level;
import net.kuruvila.php.parser.PhpLexer;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;

class LevelNode{
    Level level;
    List<LevelNode> children;

    LevelNode(Level level){
        this.level = level;
        this.children = new ArrayList();
    }

    @Override
    public String toString(){
        return "LevelNode("+level+", "+children+")";
    }
}

/**
 * This stage handles the indentations
 * 
 * @author sidharth
 */
class IndentationStage extends Stage {

    @Override
    void rule() throws RecognitionException {
        IndentationParser ip = new IndentationParser(tokens);
        ip.prog();

        //The levels need to be sorted so that the parent levels always come
        //before the child levels and individual levels are ordered according to
        //where they appear in the code.
        List<Level> l = new ArrayList<Level>(ip.levels);
        Collections.sort(l, new Comparator<Level>() {
            public int compare(Level l1, Level l2) {
                int cmp = l1.start - l2.start;
                if(cmp==0){
                    cmp = l1.end - l2.end;
                }
                return cmp;
            }
        });

        //We create a base Level as the parser does not insert this
        Level lev = new Level();
        lev.name = "base";
        lev.start = 0;
        lev.end = tokens.size()-1;
        l.add(0, lev);
        
        List<Indentation> indentations = indent("", makeTree(l));

        int WS = PhpLexer.WhiteSpace;
        for (Indentation indentation : indentations) {
            int start = indentation.start;
            int end = indentation.end;
            for (int i = start; i <= end; i++) {
                Token token = tokens.get(i);
                if(token.getType()==WS && token.getText().contains("\n")){
                    String eols = eols(token);
                    tokens.replace(token, eols+indentation.whiteSpace);
                }
            }
        }
    }

    //The make sure the line spacing remains
    //extract the \n while removing the spaces.
    private String eols(Token token) {
        String text = token.getText();
        StringBuffer sb = new StringBuffer();
        for (int j = 0; j < text.length(); j++) {
            if (text.charAt(j) == '\n') {
                sb.append("\n");
            }
        }
        String eols = sb.toString();
        return eols;
    }

    //Check if child is nested inside parent
    private boolean inside(Level parent, Level child){
        return parent.start<child.start && parent.end>child.end;
    }

    //Convert a list of Level objects into a tree
    private LevelNode makeTree(List<Level> levels){
        List<LevelNode> nodes = new ArrayList();
        nodes.add(new LevelNode(levels.get(0)));
        int n = levels.size();
        for (int i = 1; i < n; i++) {
            Level level = levels.get(i);
            while(!inside(nodes.get(nodes.size()-1).level, level)){
                nodes.remove(nodes.size()-1);
            }
            LevelNode node = new LevelNode(level);
            LevelNode parent = nodes.get(nodes.size()-1);
            parent.children.add(node);
            nodes.add(node);
        }
        return nodes.get(0);
    }

    //Use the tree generated by makeTree to generate a list of
    //Intendation objects.
    private List<Indentation> indent(String level, LevelNode parent){
        int start = parent.level.start;
        List<LevelNode> nodes = parent.children;
        List<Indentation> indentations = new ArrayList<Indentation>();
        if(nodes.size()>0){
            for (LevelNode node : nodes) {
                Indentation i = new Indentation(level, start, node.level.start-1);
                indentations.add(i);
                start = node.level.end+1;
                indentations.addAll(indent(level+"\t", node));
            }
            indentations.add(new Indentation(level, start, parent.level.end));
        }else{
            indentations.add(new Indentation(level,
                    parent.level.start, parent.level.end));
        }
        return indentations;
    }
}
